﻿#pragma once

#include "../df.h"
#include "../df_net.h"
#include "../AutoPtr.h"
#include "httpParse.h"
#include "httpHead.h"


#if DF_ENABLE_HTTP_GZIP==1
#	include "../gzip/df_zip.h"
#endif
namespace df
{
	using namespace sdf;
	class Http
	{

	public:
		//************************************
		// Method:    InitHost 初始化主机地址url,http头中的Method与Host字段
		// Parameter: CCa url 
		// Parameter: CCa method GET 或 POST
		// Parameter: HttpHead & head
		//************************************
		inline void InitHost(CCa url, CCa method, HttpHead & head)
		{
			buf_.clear();

			auto & methodStr = head.GetMap()[HTTP_keyMethod];
			methodStr.clear();
			methodStr << method << " ";

			uint16_t port = 0;
			auto & valHost = head.GetMap()[HTTP_keyHost];
			valHost.clear();

			type_ = HttpParse::ParseHttpURL(url, valHost, port, methodStr);
			if (hostName_ != valHost || port != port_)
				net_.Close();

			hostName_ = valHost;
			port_ = port;

			if (port_ != 80)
				valHost << ":" << port_;


			methodStr << " HTTP/1.1";
		}

		inline void InitHostGet(CCa url, HttpHead & head)
		{
			InitHost(url, "GET", head);
		}

		inline void InitHostPost(CCa url, HttpHead & head)
		{
			InitHost(url, "POST", head);
		}


		Http(int recvTimeOut = recvDefaultTimeOut_
			, int connectTimeOut = 0
			, int recvBufSize = recvDefaultBufSize_)
		{
			recvTimeOut_ = recvTimeOut;
			connectTimeOut_ = connectTimeOut;
			recvBufSize_ = recvBufSize;

			DF_ASSERT(recvBufSize_ > 0);
		}

		~Http()
		{
			net_.Close();
		}

		int32_t GetRecvSize()
		{
			return recvSize_;
		}

		int32_t GetRecvHeadSize()
		{
			return headSize_;
		}

		void CLose()
		{
			net_.Close();
		}

		bool Connect(const CCa & host, uint16_t port, ErrWrap err = nullptr)
		{
			port_ = port;
			host.AssignToString(hostName_);
			net_ = Net::Connect(host, port, hostIp_, connectTimeOut_, err);
			if (net_ && recvTimeOut_ > 0)
			{
				net_.SetRecvTimeOut(recvTimeOut_);

			}

			if (recvBufSize_ > 0)
			{
				net_.SetSendBufSize(recvBufSize_);
				//没用
				// net_.SetNoDelay(true);
			}
			return net_ == true;
		}

		bool Reconnect(ErrWrap err = nullptr)
		{
			return Connect(hostName_, port_, err);
		}


		bool ConnectURL(df::CCa url, ErrWrap err = nullptr)
		{
			InitHost(url);
			return Connect(hostName_, port_, err);
		}



		bool SendHead(HttpHead & head, ErrWrap err = nullptr)
		{

			for (int con = 0; con < 2; con++) {

				if (err)
					err->Init();
				if (!net_.IsConnected() && !Reconnect(err)) {
					return false;
				}
				buf_.clear();
				//填充Get或post
				auto method = head.GetMap().find(HTTP_keyMethod);
				if (method == head.GetMap().end())
				{
					DF_ERRLOG(err, ErrCode::HttpHeadInvalid, hostName_ << tcc_(" http头缺少method"));
					return false;
				}
				buf_ << method->second << "\r\n";
				
#ifndef TCP_INFO
				////把发送缓冲区设小,用来判断Send头成功与否
				auto bufLen = buf_.size() / 2;
				if (bufLen > recvBufSize_)
					bufLen = recvBufSize_;
				net_.SetSendBufSize(bufLen);
#endif
				auto res = net_.Send(&buf_[0], buf_.size());
				if (res != (int)buf_.size())
				{
					DF_LASTERR(err, hostName_ << " send head failed!");
					net_.Close();
					continue;
				}

				buf_.clear();

				HttpParse::HeadMapToStr(head, buf_);

				res = net_.Send(&buf_[0], buf_.size());
				if (res != (int)buf_.size())
				{
					DF_LASTERR(err, hostName_ << " send head failed!");
					net_.Close();
					continue;
				}

#ifndef TCP_INFO
				//还原发送缓冲区
				net_.SetSendBufSize(recvBufSize_);
#endif

				return true;
			}

			return false;
		}

		bool SendContent(const void * buff, intptr_t size, ErrWrap err = nullptr)
		{
			auto res = net_.Send(buff, size);
			if (res != size)
			{
				DF_LASTERR(err, hostName_ << size << " send content failed!");
				net_.Close();
				return false;
			}
			return true;
		}

		HttpHead & GetRecvHead()
		{
			return recvHead_;
		}


		bool RecvHead(ErrWrap err = nullptr)
		{
			InitSize();

			//循环到head接收完整
			for (;;)
			{
				if (recvBufSize_ + recvSize_ > (int)buf_.size())
					buf_.resize(recvBufSize_ + recvSize_);
				int res = net_.Recv(&buf_[0] + recvSize_, buf_.size() - recvSize_);
				if (res <= 0)
				{
					DF_LASTERR(err, hostName_ << " recv head failed!");
					net_.Close();
					return false;
				}
				recvSize_ += res;

				headSize_ = HttpParse::getHttpHeadSize(&buf_[0], recvSize_);

				//head完整
				if (headSize_ > 0)
					break;
			}

			if (!HttpParse::HeadStrToMap(&buf_[0], headSize_, recvHead_, err)) {
				net_.Close();
				return false;
			}

			recvHead_.ParseMethod();
			return true;
		}

		//************************************
		// Method:    RecvContent 接收所有content数据
		// Returns:   bool 成功与否
		// Parameter: _Func func  content结果回调:签名:void(char* buf,int size)或[&](char* buf,int size)->bool {} 返回false中断接收数据
		// Parameter: ErrWrap err
		//************************************
		template<class _Func>
		bool RecvContent(const _Func & func, ErrWrap err = nullptr)
		{
			if (recvHead_.IsChunked())
				return RecvChunkContent(func, err);

			int conLen = -1;
			//含有ContentLength
			auto contentLen = recvHead_.GetMap().find(HTTP_keyContentLength);
			if (contentLen != recvHead_.GetMap().end())
			{
				df::StrToVar(contentLen->second, conLen);
			}

			//已接收的content长度
			int recvLen = 0;
			//RecvHead中有残留的Content
			if (recvSize_ > headSize_)
			{
				recvLen = recvSize_ - headSize_;
				if (!df::ExecuteFunctor(func, &buf_[0] + headSize_, recvLen)) {
					if (err && !(*err))
						err->Set(ErrCode::manualStop, tcc_("取消下载"));
					net_.Close();
					return false;
				}
			}

			if (recvBufSize_ > (int)buf_.size())
				buf_.resize(recvBufSize_);

			//循环接收,直到recvLen>=conLen或者对方关闭链接
			for (;;)
			{
				if (conLen >= 0 && recvLen >= conLen)
					break;

				int len = net_.Recv(&buf_[0], recvBufSize_);
				if (len <= 0)
				{
					ON_SCOPE_EXIT({
						net_.Close();
					});

					if (len < 0)
					{
						DF_LASTERR(err, hostName_ << " recv content failed!");
						return false;
					}

					if (recvLen < conLen)
					{
						DF_ERRLOG(err, ErrCode::HttpContentInvalid, hostName_ << "ContentLength过长!");
						return false;
					}

					break;
				}

				recvLen += len;
				if (!df::ExecuteFunctor(func, &buf_[0], len)) {
					if (err && !(*err))
						err->Set(ErrCode::manualStop, tcc_("取消下载"));
					net_.Close();
					return false;
				}

			}

			recvSize_ = recvLen;
			return true;
		}


		//************************************
		// Method:    Get使用 GET方法获取url内容
		// Returns:   bool 成功与否
		// Parameter: CCa url
		// Parameter: HttpHead & head
		// Parameter: _Func res content结果回调:签名:void(char* buf,int size)或[&](char* buf,int size)->bool {} 返回false中断接收数据
		// Parameter: ErrWrap err
		//************************************
		template<class _Func>
		bool Get(CCa url, HttpHead & head, _Func res, ErrWrap err = nullptr)
		{
			InitHostGet(url, head);

			if (!SendHead(head, err))
				return false;

			if (!RecvHead(err))
				return false;


			return RecvContent(res, err);
		}


		//将Get结果放入std::string & out
		bool GetToString(CCa url, HttpHead & head, std::string & out, ErrWrap err = nullptr)
		{
			return Get(url, head, [&](char *buf, int len) {
				out << CCa(buf, len);
			}, err);
		}

		template<class _Func>
		bool Post(CCa url, HttpHead & head, CCa post, _Func res, ErrWrap err = nullptr)
		{
			InitHostPost(url, head);

			head.setContentLength(post.length());

			if (!SendHead(head, err))
				return false;

			if (!SendContent(post.c_str(), post.size(), err))
				return false;

			if (!RecvHead(err))
				return false;


			return RecvContent(res, err);
		}

		bool PostToString(CCa url, HttpHead & head, CCa post, std::string & out, ErrWrap err = nullptr)
		{
			return Post(url, head, post, [&](char *buf, int len) {
				out << CCa(buf, len);
			}, err);
		}

		//************************************
		// Method:    DownloadFile 下载文件(支持断点续传)
		// Returns:   bool 成功与否
		// Parameter: CCa url
		// Parameter: HttpHead & head
		// Parameter: CCa toFile 文件保存路径
		// Parameter: _Func prog 下载进度回调进度回调:bool(int64_t fileSize, int64_t sendSize),文件大小,已经传输的长度,返回false则中断传输
		// Parameter: bool tempFile 是否创建临时文件(用于支持断点续传,给文件增加.temp后缀,下载成功后重命名回来)
		// Parameter: ErrWrap err
		//************************************
		template<class _Func>
		bool DownloadFile(CCa url, HttpHead & head, CCa toFile, const _Func & prog, bool tempFile = true, ErrWrap err = nullptr)
		{

			std::string newFile;
			newFile << toFile;

			if (tempFile)
				newFile << ".temp";


			df::File fi;
			if (!fi.Open(newFile, true, !tempFile, false, err))
				return false;

			auto oldSize = fi.GetFileSize();

			if (tempFile)
			{
				if (oldSize > 0) {
					// 设置断点续传的开始位置
					head.setRange(oldSize);
				}
			}


			InitHostGet(url, head);

			if (!SendHead(head, err))
				return false;

			if (!RecvHead(err))
				return false;

			int code = recvHead_.GetHttpCode();
			auto & contentType = recvHead_.GetMap()[HTTP_keyContentType];
			//if (code == 416)/所请求的范围无法满足,可能文件已修改
			bool isHtml = contentType.find("html") != std::string::npos;
			if ((code != 206 && code != 200) || isHtml)
			{

				fi.Close();
				df::File::DelFile(newFile);
				newFile.clear();
				RecvContent([&](char *buf, int len) {
					newFile << CCa(buf, len);
				});
				DF_LOG(url << tcc_(" ") << recvHead_.GetHttpCode() << recvHead_.GetHttpMsg() << tcc_("\r\n") << newFile);

				std::string errInfo;
				if (code == 404)
					errInfo << recvHead_.GetHttpCode() << tcc_("文件不存在");
				else if (code == 200 && isHtml)
					errInfo << tcc_("下载失败:") << newFile;
				else
					errInfo << tcc_("下载失败:") << recvHead_.GetHttpCode() << recvHead_.GetHttpMsg();

				if (err)
					err->Set(ErrCode::HttpContentInvalid, std::move(errInfo));

				return false;
			}


			int64_t fileSize = recvHead_.getContentLength();

			int64_t transferLen = 0;
			// 服务器支持断点续传
			if (oldSize > 0 && (code == 206)) {
				transferLen = oldSize;
				fileSize += oldSize;
				fi.SeekEnd(0);
			}

			//接收文件内容
			bool recvRes = RecvContent([&](char *buf, int len) {

				if (!fi.Write(buf, (size_t)len, err)) {
					net_.Close();
					return false;
				}


				transferLen += len;

				if (!df::ExecuteFunctor(prog, fileSize, transferLen))
				{

					if (err && !(*err))
						err->Set(ErrCode::manualStop, tcc_("取消下载"));
					net_.Close();
					return false;
				}

				return true;
			}, err);

			if (tempFile && recvRes)
			{
				fi.Close();
				if (!df::File::Rename(newFile, toFile))
				{
					DF_LASTERR(err, newFile << tcc_("文件重命名失败"));
					return false;
				}
			}


			return recvRes;

		}

		//************************************
		// Method:    PostFile 上传文件
		// Returns:   bool 成功与否
		// Parameter: CCa url 
		// Parameter: HttpHead & head http头
		// Parameter: const _PairFile & fileName 文件名
		// Parameter: const _PairList & formText 可for迭代的键值对列表
		// Parameter: std::string & out 服务器响应的content
		// Parameter: _Func prog 上传进度回调:bool(int64_t fileSize, int64_t sendSize),文件大小,已经传输的长度,返回false则中断传输
		// Parameter: ErrWrap err
		//************************************
		template<class _Func, class _PairFile, class _PairList>
		bool PostFile(CCa url, HttpHead & head, const _PairFile & fileName, const _PairList & formText
			, std::string & out, const _Func & prog, ErrWrap err = nullptr)
		{

			InitHostPost(url, head);

			df::File fi;
			if (!fi.Open(fileName.second, false, false, true, err))
				return false;


			CCa boundary = "******au7d648hl0z3*******";
			buf2_.clear();

			for (const auto & pair : formText)
			{
				HttpParse::AddformKeyVal(pair, boundary, buf2_);
			}

			buf2_ << "--" << boundary << "\r\n"
				"Content-Disposition: form-data; name=\"" << fileName.first << "\"; filename=\"" << df::File::GetFileName(fileName.second)
				<< "\"\r\nContent-Transfer-Encoding: binary\r\n\r\n";



			//multipart/form-data结尾
			CCa conEnd = "\r\n--******au7d648hl0z3*******--\r\n";

			auto fileSize = fi.GetFileSize();

			int64_t contLen = buf2_.size() + conEnd.size() + fileSize;
			head.setContentLength((intptr_t)contLen);

			//head.SetKeepAlive();

			head.Set(HTTP_keyContentType, "multipart/form-data; boundary=******au7d648hl0z3*******");



			if (!SendHead(head, err))
				return false;

			if (!SendContent(&buf2_[0], buf2_.size(), err))
				return false;

			buf2_.reserve(recvBufSize_);

			int64_t sendSize = 0;

			while (fi.Read(&buf2_[0], (size_t)recvBufSize_, err)) {


				if (!SendContent(&buf2_[0], fi.GetSuccedByte(), err))
					return false;

				sendSize += fi.GetSuccedByte();


				if (!df::ExecuteFunctor(prog, fileSize, sendSize))
				{
					if (err && !(*err))
						err->Set(ErrCode::manualStop, tcc_("取消上传"));
					net_.Close();
					return false;
				}
			}

			if (err && *err) {
				net_.Close();
				return false;
			}


			if (!SendContent(conEnd.c_str(), conEnd.size(), err))
				return false;


			if (!RecvHead(err))
				return false;

			return RecvContent([&](char *buf, int len) {
				out << CCa(buf, len);
			}, err);
		}


	private:
		template<class _Func>
		inline bool RecvChunkContent(const _Func & func, ErrWrap err = nullptr)
		{
#if DF_ENABLE_HTTP_GZIP==1
			df::UnCompress unComp;
			auto hasCompress = recvHead_.IsGzip();
			if (hasCompress) {
				if (!unComp.Init(err))
				{
					net_.Close();
					return false;
				}
			}
#endif

			//记录已接收的content总长度
			int recvLen = 0;
			//RecvHead中有残留的Content
			if (recvSize_ > headSize_)
			{
				recvLen = recvSize_ - headSize_;
			}


			//本次接收的长度
			int curLen = recvLen;
			recvLen = 0;
			for (;;)
			{
				auto startBuf = &buf_[0] + headSize_;
				int rnLen = HttpParse::HasRN(startBuf, curLen);
				//Chunk断包
				if (rnLen <= 0)
				{
					if (curLen + headSize_ + 100 > (int)buf_.size())
					{
						buf_.resize(buf_.size() + recvSize_);
						startBuf = &buf_[0] + headSize_;
					}

					int len = net_.Recv(startBuf + curLen, (int)buf_.size() - headSize_ - curLen);
					if (len <= 0)
					{
						DF_LASTERR(err, hostName_ << "接收Content失败!");
						net_.Close();
						return false;
					}

					curLen += len;
					continue;
				}

				//解析出一个chunk
				CCa chunkStr(startBuf, rnLen - 2);
				int chunkLen = 0;
				df::StringHexToDigit(chunkStr, chunkLen);

				//最后一个chunk接收完成,chunk最后还有\r\n
				if (chunkLen == 0)
				{
					//\r\n完成
					if (curLen - rnLen == 2)
						break;


					if (curLen - rnLen < 2) {
						int len = net_.Recv(startBuf, recvBufSize_);
						if (len != curLen - rnLen)
						{
							DF_LASTERR(err, hostName_ << "Content chunk收尾错误!");
							net_.Close();
							return false;
						}
						break;
					}

					net_.Close();
					DF_ERRLOG(err, ErrCode::HttpContentInvalid, hostName_ << "Content chunk长度不对!");
					return false;
				}

				//每个chunk后跟\r\n所以+2;
				chunkLen += 2;

				for (;;)
				{
					//排除chunk头之后的接收长度
					int skipChunkLen = curLen - rnLen;
					//chunk没接收完,循环到完成
					if (chunkLen > skipChunkLen)
					{

						if (headSize_ + chunkLen + curLen > (int)buf_.size())
						{
							COUT(tcc_("*********内存增长,chunkLen:") << chunkLen << tcc_("curLen:") << curLen << tcc_("buf:") << buf_.size());
							buf_.resize(headSize_ + chunkLen + curLen + 4);
							if (buf_.size() < buf_.capacity() - 100)
								buf_.resize(buf_.capacity() - 4);
							startBuf = &buf_[0] + headSize_;
						}


						int len = net_.Recv(startBuf + curLen, (int)buf_.size() - headSize_ - curLen);
						if (len <= 0)
						{
							DF_LASTERR(err, hostName_ << "接收Content失败!");
							net_.Close();
							return false;
						}

						curLen += len;
						continue;
					}

					break;
				}

				//每个chunk后有个多余的\r\n所以-2;
				recvLen += chunkLen - 2;
				COUT(tcc_("*************chunk包,chunkLen:") << chunkLen);


				auto fromBuf = startBuf + rnLen;
				auto bufLen = chunkLen - 2;
#if DF_ENABLE_HTTP_GZIP==1
				//解压缩
				if (hasCompress)
				{
					if ((int)buf2_.capacity() < bufLen * 2)
					{
						buf2_.reserve(bufLen * 2);
					}
					bool uncomRes = unComp.UnCompressStart(fromBuf, bufLen, &buf2_[0], buf2_.capacity()
						, [&](uint32_t len) {

						if (!df::ExecuteFunctor(func, &buf2_[0], len))
						{
							if (err && !(*err))
								err->Set(ErrCode::manualStop, tcc_("取消下载"));
							net_.Close();
							return false;
						}
						return true;
					}, err);

					if (!uncomRes)
					{
						net_.Close();
						return false;
					}
				}
				else
				{
#endif
					if (!df::ExecuteFunctor(func, fromBuf, bufLen))
					{
						if (err && !(*err))
							err->Set(ErrCode::manualStop, tcc_("取消下载"));
						net_.Close();
						return false;
					}
#if DF_ENABLE_HTTP_GZIP==1
				}
#endif



				//将缓冲区剩下的部分拷贝到首部,重新开始循环
				curLen = curLen - rnLen - chunkLen;
				memcpy(startBuf, startBuf + rnLen + chunkLen, curLen);
				COUT(tcc_("*************移动内存,curLen:") << curLen);
			}



			recvSize_ = recvLen;
			return true;


		}
	private:
		Net net_;
		//接收缓冲区
		std::string buf_;
		//接收缓冲区2(用于解压缩与post上传文件)
		std::string buf2_;
		//主机名
		std::string hostName_;

		//url参数
		std::string urlPara_;

		//主机ip
		int32_t hostIp_ = 0;
		//对方端口
		uint16_t port_ = 80;

		HttpType type_ = HttpType::typeHttp;

		static const int recvDefaultBufSize_ = 1024 * 8;
		//接收缓冲区长度
		int recvBufSize_ = recvDefaultBufSize_;

		static const int recvDefaultTimeOut_ = 40;
		//接收超时值(大于0有效)
		int recvTimeOut_ = recvDefaultTimeOut_;
		//发送超时值
		int sendTimeOut_ = recvDefaultTimeOut_;

		int connectTimeOut_ = recvDefaultTimeOut_;

		//收到的http头长度
		int headSize_ = 0;

		//收到的Content的长度
		int recvSize_ = 0;

		//收到的http头
		HttpHead recvHead_;


		DF_PTR_REF_COUNT;

		void InitSize()
		{
			headSize_ = 0;
			recvSize_ = 0;
			recvHead_.Clear();
		}

		inline void InitHost(CCa url)
		{
			hostName_.clear();
			type_ = HttpParse::ParseHttpURL(url, hostName_, port_, urlPara_);
		}


		DF_DISABLE_COPY_ASSIGN(Http);
	};
}
